I had to go through an exercise at work recently of converting our ASP.Net Web API from version 1 to version 2. One of the great features in version 2 is the automatic help system, allowing you to very easily document your API just by using the method comments. This system is awesome as long as you’re fine with showing all of your methods to everyone with access the help page. However, we needed to hide some of these as they were not for public consumption, potentially revealing how some of our internal LOB systems operate. We already have some custom access control in place, but the help page system is oblivious to this.

So to customize the help system to hide some methods, or show depending upon access level, you’re left with two options.

  1. Create two separate APIs, one for your public methods and another for your private/restricted methods. This is a really good way of doing it, if the option is available to you.
  2. Customize the underlying help page system with a system that designates which methods should be public, and which should be private. Not a beginners level exercise.

Since our API was being upgraded and several applications were already in place using this API, we didn’t have much choice and were left with option #2 as really our only choice. I’m going to detail what was involved in this process. It is by no means perfect, so if you have suggestions on how it can be improved, please let me know. I would love to discuss it. I’m using images for most of the code, there is a link to a sample project at the end of this post. If you’re like me and hate when people do this, I apologize up front. There were just too many spots where I had to make changes, and I’m not a good enough technical writer to make this post copy and paste-able without making a mess of it. So lets get into it.

The Fine Print

First things first, a couple of things I won’t cover in this. You need to be familiar with enabling the help system to generate and read from an XML file, that underpins this whole thing. Secondly, I won’t cover any sort of authentication. You need to protect the private API help page and the XML documentation file in some way. Our API lives on the company servers, so I used Windows Authentication, but any of the other normal ASP.Net authentication options should work as well. Lastly, I’m assuming that you’re starting with a Web API 2 project, I’m not covering creating or upgrading one. With that out of the way, lets get into it.

Laying the Groundwork

The basis of the entire help system is the ApiExplorer, which inherits from IApiExplorer. We’ll need to inherit from these appropriately. The ApiExplorer is automatically wired into Web API as a service, so we’ll need to replace it with our customized version. How difficult can that be? As it turns out, far more than I had expected.

Let’s start by creating the filters that do the business of marking which API method should be shown in the public help section, and which should be shown in the private. With the API I was working on, I didn’t want anything to be accidentally exposed publicly by a developer forgetting to apply an attribute, so it defaults everything to private. In the sample project, there are comments indicating how to flip this and make everything public by default in the CustomApiExplorer.cs file. The first thing to to then is to create the attribute we use to mark the methods that are public.

API_PublicAccessLevel

Create a PrivateAccessLevelAttribute with the exact same OnActionExecuting method. You don’t actually use it unless you flip the logic to default to public, but it’s nice to have in case you ever want to be explicit. I have them inheriting from an interface, IAccessLevelAttribute, which actually isn’t needed. I was just trying to future proof with that. So now that we have our attribute, lets put it on some methods so we’ll have something to work with. ValuesController, here we come!

API_ValuesAttribute

Wow, that’s some boring code, but it gets the point across. The GET methods have the PublicAccessLevel attribute on them, and the POST, PUT, and DELETE will be left to default to private. So now we can start in on customizing the ApiExplorer. Almost all of the remaining changes will happen in the help pages area. (Areas/HelpPages in Solution Explorer.) I love me some Interface, so lets create one in there, called ICustomApiExplorer that will extend the ApiExplorer just a little bit.

API_CustomApiExplorer

Not surprisingly, our we’re going to create CustomApiExplorer next, which will inherit from ICustomApiExplorer, as well as ApiExplorer.

API_CustomApiExplorer2

You should be able to see the comments I mentioned earlier about flipping the logic to default the access level to public in there. There are also comments about why it’s keeping a full copy of the ApiExplorer rather that just inheriting from it. Maybe there is a way to inherit from it and get around the lazy loading issues, but I wasn’t clever enough (or patient enough) to figure it out.

The Supporting Details

If you were to copy the CustomApiExplorer class in, you would notice that the HasPublicAccessLevelAttribute method doesn’t exist. Let’s create a class for these extension methods, let’s call it AccessLevelExtensions, in the same spot as our CustomApiExplorer.

API_AccessLevelExtensions

It’s important to note the reference count on those. The only one currently being used is the HasPublicAccessLevelAttribute method that was in our CustomApiExplorer class. If you wanted to make a controller level method you would need to override the ShouldExploreController method from ApiExplorer in CustomApiExplorer in a similar fashion to ShouldExploreAction. This would utilize the other extension method overload that carries the HttpControllerDescriptor parameter. Like I mentioned previously, I want a developer to have to mark each and every method that they want in the public help page, so I don’t have the controller level option implemented for the attributes. In my experience, it’s very easy to miss noticing an attribute on the controller level, so I’m not even allowing that option. The extension methods for the private access level attribute are provided for completeness, in case you want to default to public.

Let’s finish up the ApiExplorer customization by replacing it with our CustomApiExplorer in the Web API services.

API_HelpPageConfig

I deleted most of the commented out code that lives in there when you create a new project so that you can now see where I’ve enabled the path to the XML documentation file. The ApiExplorer replacement happens in the two lines of code below that. This is the key piece that holds everything together, and was honestly the toughest bit of the entire project to figure out.

Displaying the Private Help Page

The toughest bits are past us, and we’re just left with the presentation of the private help page. Now we’ll create a new Action in the HelpController for our private help page.

API_PrivateHelpPage

You need to fully qualify the Authorize attribute as the API one is also available, so it doesn’t know which one to use. Now we need to change the index action to only show methods with the public access attribute.

API_HelpPageController

On both of those, note the GetCustomApiExplorer extension method. It is visible in the extension methods above, but I didn’t mention it then. Since we replaced the ApiExplorer in the services with our CustomApiExplorer, we need to get that type out. This extension method is fundamentally the same as what is normally there, GetApiExplorer, just modified to return our CustomApiExplorer type instead.

Now generate the view for the Private Help Page. I’m a fan of right clicking in the controller method and going to Add View. Once it’s out there, copy and paste the code from the Index view, changing the Page Title and Description to indicate that it is the private help page.

Now we need to be able to reach the Private Help page. How you make this available is up to you and the presentation of the main area of your project. For the demo project, or for any project that is using the default Web API project template, you can add it to the menu in the main layout view. (This live in project root/Views/Shared/_Layout.cshtml.)

API_HelpMenu

The list item below the comment replaces the list item that previously only went to the help page index. This will give you the slick Bootstrap menu instead. It should now be ready to run!

Wrapping Up

As mentioned, a link to the sample project is below. I hope this helps out anyone that runs into the same project requirement that I did. It’s not an easy exercise, but it was an interesting one. There was quite a bit of digging into the Web API source code, which never would have been possible before Microsoft’s shift towards being more open. I think I have contact info on this blog, but if not, you can usually hit me up on Twitter (@slynch78).

Download

Sample Project

I think this is the second time I’ve struggled with this. When you have a GET parameter in ASP.Net Web API (or even in MVC) that has a period in it, it does not work with the new project web.config. Examples of this would be an email address (api/user/foo@test.com), a price (api/prices/0.00), or a custom ID (api/widget/QWERTY.67890). All of these will return a very unhelpful 404 error about the resource not being found. This is because IIS is trying to map everything after the period as an extension, and not finding it. The easy fix is to add the following to the web.config in system.webServer.

<modules runAllManagedModulesForAllRequests=“true” />

Solution found here:
WebAPI 2 Route Attribute with string parameter containing a period doesn’t bind

I’ve been doing more work in the ASP.Net Web API Beta that’s part of MVC 4. One of the rather odd things I’ve come across is that despite the automatic content negotiation, the ASP.Net custom error page (yellow screen of death) still gets returned when an error occurs by default. If all you want to do is fix this, you can disable it and pass it along with the following setting in the system.webServer node in the web.config:

<httpErrors existingResponse="PassThrough" />

You still get the correct http status code, and now the error message will get serialized to a string and returned in the requested content type.

My desired scenario was slightly more complex, though. In my view, a proper API should have a consistent error format and some meaningful error messages. So I wanted to return a custom class I wrote with all of the details that I wanted returned, and have it serialized by content negotiation into the correct type. Something like the following:

[Serializable]
[DataContract] public class MyCustomError {
[DataMember] public int Code { get; set; }
[DataMember]
public string Headline { get; set; }
  [DataMember]
public string Message { get; set; }

public MyCustomError() { }
public MyCustomError(int ErrorCode, string ErrorTitle, string ErrorDetails) { Code = ErrorCode; Message = ErrorDetails; Headline = ErrorTitle; } }

Take note of the Serializable attribute and DataContract bits, they are important on there. So now that I have the class, I need to get it returned. Now you need another class that inherits from the ExceptionFilterAttribute. This will be what actually configures the error response back to the client when an error occurs.

    public class CustomResponseException : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext context)
        {
            HttpStatusCode responseStatus = HttpStatusCode.InternalServerError;
            if (context.Exception.Data.Contains("StatusCode"))
            {
                responseStatus = (HttpStatusCode)context.Exception.Data["StatusCode"];
            }
            if (context.Exception.Data.Contains("MyCustomError"))
            {
                context.Response = context.Request.CreateResponse<MyCustomError>(responseStatus, (MyCustomError)context.Exception.Data["MyCustomError"]);
            }
            else
            {                
                context.Response = context.Request.CreateResponse<MyCustomError>(responseStatus, new MyCustomError(ErrorNumber.UNSPECIFIEDERROR.ConvertToInt(), "Unknown error", "An error occurred."));
} base.OnException(context); } }

When I throw an error in code, I’m setting two pieces of data into the exception so that I can use it in this class. I put an instance of MyCustomError into the exception Data dictionary key “MyCustomError” so that I can return a specific message to the client. The second is a custom status code for cases like a 401 Unauthorized or a 404 NotFound, but much of the time I don’t set it as the 500 error is appropriate. Then I take these and put them in the context.Response, which along with the PassThrough attribute set in the web.config and the Serializable attribute on MyCustomError lets it automatically serialize to either XML or JSON according the client request type. (ErrorNumber in the code above is an enum that I’m using internally to signify the various custom error numbers for my API.) 

There’s one last step to actually get this hooked up. You have to either enable the CustomResponseException attribute globally, on a class level, or on a method level. If you want to do it class by class or method by method basis, use it as you would any attribute, by adding [CustomResponseException] above the method or class name. (Just like the [Serializable] on the MyCustomError class.) If you want it marked globally, go to the Application_Start method in Global.asax and add the line below.

GlobalConfiguration.Configuration.Filters.Add(new CustomResponseException());

Reference the links below for more information on the various parts of this.

Exception Filters

Another Custom Type Approach

httpErrors PassThrough setting

HttpStatusCode enum

Notes: This post was written during the Beta. I’ve updated the code for the release candidate on 2012-06-11.

I’ve been playing with the new MVC 4 Web API bits this week, and it seems really great so far. After playing around a bit, one of the first things that I realized I would need to do would be to fix the JSON serialization date format. Below are links to a couple of great articles that help when switching it from the DataContractSerializer to JSON.Net while it’s in Beta. Apparently JSON.Net will be there as the default once it releases, though.

Using JSON.Net with ASP.Net Web API

On the nightmare that is JSON Dates…

Media Formatters